{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c6640d7f",
   "metadata": {},
   "source": [
    "上一节，通过建立词表，我们学会了用词元和词嵌入来表示文本序列数据。这些有序的词元实际上相当于一系列的离散数据。我们可以用有序的词元序列$x_1,x_2,\\dots,x_t$来表示一个句子，此时的$x_t$就是t时刻的观测标签。当我们去计算$x_1,x_2,\\dots,x_t$这样一个文本序列出现的联合概率的时候，语言模型就诞生了。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1552615a",
   "metadata": {},
   "source": [
    "## 9.3.1 什么是语言模型"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e90563fc",
   "metadata": {},
   "source": [
    "简单地说，语言模型就是用来计算一个句子的概率的模型，也就是判断一句话是否是人话的概率。\n",
    "\n",
    "给定一个词汇集合V，对于一个由V中的词构成的序列$S={x_1,\\dots,x_t},x_i \\in V$，统计语言模型赋予这个序列一个概率$p(S)$，来衡量$S$符合自然语言的语法和语义规则的置信度，这个$p(S)$就是语言模型，也叫统计语言模型（Statistical Language Model）。\n",
    "\n",
    "这里$p(S)$可以用下面这个公式表示：\n",
    "    $$P(S) = P(x_1,\\dots,x_t) = P(x_1)P(x_2|x_1) \\dots P(x_t|x_1,x_2,\\dots,x_{t-1})$$\n",
    "\n",
    "从公式里我们看出来，随着句子长度的增加，条件概率$P(x_1)P(x_2|x_1) \\dots P(x_t|x_1,x_2,\\dots,x_{t-1})$的计算会越来越困难。同时，如果语料库不够大,或者词串比较长的话，大部分的词的组合将不会出现，最终会导致数据异常稀疏。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89bfd6c5",
   "metadata": {},
   "source": [
    "## 9.3.2 马尔可夫假设和N元语言模型"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a44de4e9",
   "metadata": {},
   "source": [
    "为了解决计算困难的问题，马尔可夫假设登场了。马尔可夫假设是语言模型中一个重要的概念。它指的是一个单词出现的概率只与前面几个单词有关，与前面的所有单词无关。\n",
    "\n",
    "例如，有这么一个句子，如果我们已经知道了前几个字 \"我家猫吃\"，此时根据马尔可夫假设，我们可以认为下一个字 \"鱼\" 出现的概率只与前两个字 \"猫吃\" 有关，而与句子中其他单词无关。\n",
    "\n",
    "如果我们假设一个词的出现与它周围的词是独立的，这样的模型就被称为一元语言模型（unigram）,用公式表示如下：\n",
    "\n",
    "$$ P(S) = P(x_1,\\dots,x_t) = P(x_1)P(x_2) \\dots P(x_t)$$\n",
    "\n",
    "如果我们假设一个词的出现只依赖于它前一个词，而与更前面的词无关，我们就得到了二元语言模型（bigram），公式表示如下：\n",
    "\n",
    "$$ P(S) = P(x_1,\\dots,x_t) = P(x_1)P(x_2|x_1)P(x_3|x_2) \\dots P(x_t|x_{t-1})$$\n",
    "\n",
    "同理，三元语言模型的公式就呼之欲出了：\n",
    "\n",
    "$$ P(S) = P(x_1,\\dots,x_t) = P(x_1)P(x_2|x_1)P(x_3|x_2,x_1) \\dots P(x_t|x_{t-1},x_{t-2})$$\n",
    "\n",
    "由此我们可以得到N元语言模型的一般表达式：\n",
    "\n",
    "$$ P(S) = P(x_1,\\dots,x_t) = P(x_1)P(x_2|x_1)P(x_3|x_2,x_1) \\dots P(x_n|x_{n-1},\\dots,x_1)\\dots P(x_t|x_{t-1},\\dots,x_{t-n+1})$$\n",
    "\n",
    "一般来说，N元模型（ngram）就是假设当前词的出现概率只与它前面的N-1个词有关。而这些概率参数都是可以通过大规模语料库来计算的。比如最简单的一元模型，我们假设语料库的总词数为T，则有：\n",
    "$$ p(x_t) = \\frac{count(x_t)}{T} $$\n",
    "\n",
    "同理，如果是三元模型，则有：\n",
    "\n",
    "$$ P(x_t| x_{t-1},x_{t-2}) \\approx \\frac {count(x_{t-2},x_{t-1},x_t)}{count(x_{t-2},x_{t-1})}$$\n",
    "\n",
    "由此可以得到N元模型中，我们可以基于n-gram出现的频率来计算概率：\n",
    "\n",
    "$$ P(x_t| x_{t-1},\\dots,x_{t-n+1}) \\approx \\frac {count(x_{t-n+1},\\dots,x_{t-1},x_t)}{count(x_{t-n+1},\\dots,x_{t-1})}$$\n",
    "\n",
    "马尔可夫假设是语言模型中常用的一个假设，可以帮助我们简化计算，使得我们能够用更简单的方法来计算一句话中下一个单词的概率。\n",
    "\n",
    "但是，马尔可夫假设并不能完全准确地描述自然语言，因为在自然语言中，一个单词出现的概率往往还与句子的整体结构有关。因此，在实际应用中，我们常常需要对马尔可夫假设进行修正，以更准确地描述自然语言。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d61e665e",
   "metadata": {},
   "source": [
    "## 9.3.3 语言模型平滑法"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d7291b22",
   "metadata": {},
   "source": [
    "计算困难的问题解决了，接下来要面对的是数据稀疏问题。当我们的语料率不够大的时候，如果我们直接使用训练语料库中出现过的单词来计算概率，就会有一个问题：如果训练语料库中没有出现过的单词出现在句子中，我们就无法计算它的概率。这个问题在自然语言处理领域常被称为OOV问题，也就是out of vocabulary。\n",
    "\n",
    "为了解决这个问题，我们可以使用平滑技术来平滑语言模型的概率分布。平滑技术可以通过在计算概率时给所有单词加上一个小的概率来解决这个问题。这样，即使训练语料库中没有出现过的单词出现在句子中，我们也可以计算它的概率。\n",
    "\n",
    "常用的平滑方法有很多，例如拉普拉斯平滑、平均概率平滑和Good-Turing平滑等。这些平滑方法都可以帮助我们在计算概率时避免因训练语料库中没有出现过的单词而产生的问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd58f22d",
   "metadata": {},
   "source": [
    "### 9.3.3.1 拉普拉斯平滑"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cdd58812",
   "metadata": {},
   "source": [
    "拉普拉斯平滑是一种常用的平滑方法，它可以用来平滑语言模型的概率分布。\n",
    "\n",
    "在计算一个单词的概率时，拉普拉斯平滑会将所有单词的计数加上一个常数 k，然后将所有单词的计数都除以总单词数加上单词表大小乘 k 的和。这样，即使训练语料库中没有出现过的单词出现在句子中，我们也可以计算它的概率。它的公式如下：\n",
    "\n",
    "$$p(x) = \\frac{count(x)+k}{T+k\\times V}$$\n",
    "\n",
    "其中，P(x) 是单词 x 的概率，count(x) 是单词 x 在训练语料库中出现的次数，T 是训练语料库中单词的总数，k 是一个平滑参数，V 是单词表的大小。单词 w 的概率等于它在训练语料库中出现的次数加上 k，除以总单词数加上单词表大小乘 k 的和。这样，即使训练语料库中没有出现过的单词出现在句子中，我们也可以计算它的概率。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24cd81f9",
   "metadata": {},
   "source": [
    "## 9.3.4 语言模型的评价"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f41becf6",
   "metadata": {},
   "source": [
    "当我们构造好了一个语言模型后，如何知道模型好还是不好呢？\n",
    "\n",
    "一种比较常用的方法是计算模型的困惑度（Preplexity），它的基本思想也很简单，就是给测试集赋予较高概率值的语言模型是更高的语言模型。\n",
    "\n",
    "ngram语言模型的困惑度计算公式如下：\n",
    "\n",
    "$$PPL(X) = p(x_1,x_2,\\dots,x_n)^{-\\frac{1}{n}}=\\sqrt[n]{\\frac{1}{p(x_1,x_2,\\dots,x_n)}} $$\n",
    "\n",
    "相当于测试集概率的倒数再用单词数做归一化。因为单词序列的条件概率越高，困惑度越低。测试集上困惑度越低，说明模型效果越好。\n",
    "\n",
    "不过，困惑度指标一般只能作为评价模型效果的一个参考，因为某些时候，一些困惑度高de"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0e8c1922",
   "metadata": {},
   "source": [
    "**梗直哥提示：本节我们学习了语言模型相关的知识，这里讲的虽然是相对传统的语言模型，但是其思想却是不变的，在深度学习语言模型大行其道的当下，掌握语言模型的基本思想，依然具有重要意义。更加详细的知识点的掌握，有赖于你在实战中总结经验，慢慢就熟悉了。当然，如果你想大幅节省时间，解答自己在学习中的各种困惑，欢迎选修《梗直哥深度学习：python实战》。**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "835627c2",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
